Розкрийте можливості Temporal API Duration в JavaScript. Цей посібник досліджує математику часових інтервалів, пропонуючи приклади та поради для розробників.
Опановуємо арифметику з Temporal Duration в JavaScript: Глобальний посібник з математики часових інтервалів
У світі веб-розробки, що постійно розвивається, точне та надійне керування часом має першочергове значення. Незалежно від того, чи ви розраховуєте терміни виконання проєктів у різних часових поясах, керуєте поновленням підписок або плануєте глобальні події, точна математика часових інтервалів є вкрай важливою. Сучасний JavaScript представив потужний інструмент для цієї мети: Temporal API, а саме його об'єкт Duration. Цей вичерпний посібник розкриє таємниці арифметики з JavaScript Temporal Duration, надаючи глобальний погляд на його можливості та практичне застосування.
Потреба в надійному керуванні часом
Історично склалося так, що вбудований об'єкт Date у JavaScript був джерелом розчарувань для розробників. Його непослідовність, відсутність незмінності та складне керування часовими поясами й переходом на літній час призводили до численних помилок і постійної потреби у зовнішніх бібліотеках. Temporal API, запропонований стандарт для ECMAScript, має на меті виправити ці проблеми, пропонуючи більш інтуїтивно зрозумілий, послідовний і потужний спосіб роботи з датами, часом і тривалістю.
Для глобальної аудиторії ці виклики лише посилюються. Уявіть:
- Менеджер проєкту в Берліні розраховує час доставки вантажу в Токіо, враховуючи різницю в часових поясах і можливі затримки.
- Фінансовий аналітик у Нью-Йорку визначає точний період між двома виплатами відсотків, зробленими в різних фінансових кварталах у Європі.
- Маркетингова команда в Сінгапурі планує запуск глобальної кампанії, забезпечуючи її відповідність піковим часам перегляду в Північній Америці, Європі та Азії.
Ці сценарії підкреслюють критичну потребу в стандартизованому, однозначному підході до математики часових інтервалів. Об'єкт Duration з Temporal API створений, щоб задовольнити цю потребу.
Знайомство з об'єктом JavaScript Temporal Duration
Об'єкт Temporal.Duration представляє кількість часу, незалежну від будь-якої конкретної точки в часі. Це міра минулого часу, наприклад, '2 роки, 3 місяці та 4 дні'. На відміну від попередніх підходів, які часто змішували тривалість із точками в часі, Temporal.Duration зосереджується виключно на величині часу. Це розділення є ключем до його потужності та простоти.
Ключові компоненти тривалості
Об'єкт Temporal.Duration може представляти час у різних одиницях. Основні одиниці, які він підтримує:
- Роки (
years) - Місяці (
months) - Тижні (
weeks) - Дні (
days) - Години (
hours) - Хвилини (
minutes) - Секунди (
seconds) - Мілісекунди (
milliseconds) - Мікросекунди (
microseconds) - Наносекунди (
nanoseconds)
Об'єкт Duration може бути додатним (представляючи рух часу вперед) або від'ємним (представляючи рух назад). Важливо також зазначити, що Temporal.Duration є незмінним (immutable). Після створення його значення не можна змінити. Будь-яка операція, що виглядає як зміна тривалості, насправді повертає новий об'єкт Duration.
Створення об'єктів Temporal Duration
Ви можете створювати об'єкти Temporal.Duration кількома способами, кожен з яких підходить для різних сценаріїв.
1. Використання методу Temporal.Duration.from()
Це найуніверсальніший метод, що дозволяє створювати тривалість з різних вхідних даних, включаючи об'єктний літерал або рядок тривалості у форматі ISO 8601.
З об'єктного літерала:
Вкажіть одиниці, які ви хочете включити, як властивості об'єкта.
const twoYearsThreeMonths = Temporal.Duration.from({
years: 2,
months: 3
});
console.log(twoYearsThreeMonths);
// Temporal.Duration { years: 2, months: 3, ... }
const oneDayEightHours = Temporal.Duration.from({
days: 1,
hours: 8,
minutes: 30
});
console.log(oneDayEightHours);
// Temporal.Duration { days: 1, hours: 8, minutes: 30, ... }
const negativeDuration = Temporal.Duration.from({
hours: -5,
minutes: -15
});
console.log(negativeDuration);
// Temporal.Duration { hours: -5, minutes: -15, ... }
З рядка тривалості у форматі ISO 8601:
Стандарт ISO 8601 надає компактне рядкове представлення для тривалостей. Формат такий: PnYnMnDTnHnMnS, де:
Pпозначає початок періоду тривалості.Yпредставляє роки.Mпредставляє місяці.Dпредставляє дні.Tвідокремлює компоненти дати від компонентів часу.Hпредставляє години.Mпредставляє хвилини.Sпредставляє секунди.
Зверніть увагу, що 'M' після 'T' означає хвилини, тоді як 'M' перед 'T' — місяці. Одиниці часу (години, хвилини, секунди) є необов'язковими і з'являються лише за наявності ненульового значення.
const isoDuration1 = Temporal.Duration.from('P1Y2M3DT4H5M6S');
console.log(isoDuration1);
// Temporal.Duration { years: 1, months: 2, days: 3, hours: 4, minutes: 5, seconds: 6, ... }
const isoDuration2 = Temporal.Duration.from('P10DT5H'); // 10 days, 5 hours
console.log(isoDuration2);
// Temporal.Duration { days: 10, hours: 5, ... }
const isoDuration3 = Temporal.Duration.from('P3M'); // 3 months
console.log(isoDuration3);
// Temporal.Duration { months: 3, ... }
// Invalid ISO 8601 strings will throw an error.
// Temporal.Duration.from('PT10M5S'); // This is valid
// Temporal.Duration.from('10M'); // This is not valid without 'P'
2. Використання конструктора Temporal.Duration()
Конструктор дозволяє пряме створення екземпляра, але зазвичай рекомендується використовувати from(), оскільки він пропонує більшу гнучкість і кращу обробку помилок для недійсних вхідних даних.
const constructorDuration = new Temporal.Duration(0, 0, 0, 1, 2, 3); // years, months, weeks, days, hours, minutes
console.log(constructorDuration);
// Temporal.Duration { years: 0, months: 0, weeks: 0, days: 1, hours: 2, minutes: 3, ... }
// Note: The constructor takes arguments in a fixed order (years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
// Providing fewer arguments means later units are treated as zero.
const partialDuration = new Temporal.Duration(1, 6); // 1 year, 6 months
console.log(partialDuration);
// Temporal.Duration { years: 1, months: 6, ... }
Доступ до компонентів тривалості
Коли у вас є об'єкт Temporal.Duration, ви можете отримати доступ до його окремих компонентів за допомогою властивостей:
const myDuration = Temporal.Duration.from({
years: 5,
days: 10,
hours: 12,
minutes: 45
});
console.log(myDuration.years);
// 5
console.log(myDuration.days);
// 10
console.log(myDuration.hours);
// 12
console.log(myDuration.minutes);
// 45
console.log(myDuration.seconds); // Units not specified are 0
// 0
Арифметика з Temporal Duration: Основні операції
Справжня сила об'єкта Temporal.Duration полягає в його арифметичних операціях. Ці операції дозволяють додавати, віднімати, множити та ділити тривалості, забезпечуючи точний контроль над часовими інтервалами.
1. Додавання тривалостей (add())
Метод add() дозволяє об'єднувати два об'єкти Temporal.Duration. При додаванні тривалостей одиниці агрегуються. Наприклад, додавання '1 року' та '2 місяців' призводить до тривалості '1 рік, 2 місяці'.
const duration1 = Temporal.Duration.from({ days: 10, hours: 5 });
const duration2 = Temporal.Duration.from({ days: 5, hours: 10 });
const totalDuration = duration1.add(duration2);
console.log(totalDuration);
// Temporal.Duration { days: 15, hours: 15, ... }
const duration3 = Temporal.Duration.from({ years: 1, months: 6 });
const duration4 = Temporal.Duration.from({ months: 8, days: 15 });
const combinedDuration = duration3.add(duration4);
console.log(combinedDuration);
// Temporal.Duration { years: 1, months: 14, days: 15, ... }
// Note: This is a simple aggregation. Temporal will handle unit rollovers (e.g., 14 months becoming 1 year and 2 months) when interacting with PlainDate/Time objects.
// Adding a negative duration is equivalent to subtraction
const duration5 = Temporal.Duration.from({ hours: 3 });
const duration6 = Temporal.Duration.from({ hours: -1 });
const result = duration5.add(duration6);
console.log(result);
// Temporal.Duration { hours: 2, ... }
2. Віднімання тривалостей (subtract())
Метод subtract() працює аналогічно до add(), але виконує віднімання.
const durationA = Temporal.Duration.from({ days: 20, hours: 10 });
const durationB = Temporal.Duration.from({ days: 5, hours: 3 });
const remainingDuration = durationA.subtract(durationB);
console.log(remainingDuration);
// Temporal.Duration { days: 15, hours: 7, ... }
// Subtracting a duration that results in a negative value
const durationC = Temporal.Duration.from({ minutes: 30 });
const durationD = Temporal.Duration.from({ minutes: 45 });
const negativeResult = durationC.subtract(durationD);
console.log(negativeResult);
// Temporal.Duration { minutes: -15, ... }
3. Заперечення тривалості (negated())
Метод negated() повертає новий об'єкт Duration з усіма інвертованими компонентами (додатні стають від'ємними, а від'ємні — додатними).
const positiveDuration = Temporal.Duration.from({ hours: 10, minutes: 30 });
const negativeDuration = positiveDuration.negated();
console.log(negativeDuration);
// Temporal.Duration { hours: -10, minutes: -30, ... }
const alreadyNegative = Temporal.Duration.from({ days: -5 });
const nowPositive = alreadyNegative.negated();
console.log(nowPositive);
// Temporal.Duration { days: 5, ... }
4. Абсолютне значення тривалості (abs())
Метод abs() повертає новий об'єкт Duration, у якому всі компоненти зроблені невід'ємними. Це корисно, коли вас цікавить лише величина часового інтервалу, незалежно від його напрямку.
const negativeDuration = Temporal.Duration.from({ hours: -8, minutes: -20 });
const absoluteDuration = negativeDuration.abs();
console.log(absoluteDuration);
// Temporal.Duration { hours: 8, minutes: 20, ... }
5. Множення тривалостей (multiply())
Метод multiply() дозволяє масштабувати тривалість на задане число. Це надзвичайно корисно для таких завдань, як обчислення загального часу для повторюваних подій або визначення майбутніх етапів на основі базового інтервалу.
const dailyDuration = Temporal.Duration.from({ days: 1 });
const twoWeeks = dailyDuration.multiply(14);
console.log(twoWeeks);
// Temporal.Duration { days: 14, ... }
const hourlyIncrement = Temporal.Duration.from({ hours: 1 });
const workWeek = hourlyIncrement.multiply(40);
console.log(workWeek);
// Temporal.Duration { hours: 40, ... }
const projectPhase = Temporal.Duration.from({ months: 2 });
const fullProject = projectPhase.multiply(3);
console.log(fullProject);
// Temporal.Duration { months: 6, ... }
// Multiplication can also be done with negative numbers
const futureEvent = Temporal.Duration.from({ days: 5 }).multiply(-2);
console.log(futureEvent);
// Temporal.Duration { days: -10, ... }
6. Ділення тривалостей (divide())
Метод divide() дозволяє ділити тривалість на задане число. Це корисно для таких завдань, як визначення середньої тривалості події або розбиття загального часу на менші рівні частини.
Важливе зауваження щодо ділення: Ділення в Duration розроблено так, щоб повертати ціле число одиниць для кожного компонента. Будь-яка дробова частина зазвичай відкидається (округлюється до меншого). Для сценаріїв, що вимагають дробових результатів, зазвичай слід працювати з об'єктами PlainDateTime або Instant, а потім обчислювати результуючу тривалість.
const totalWorkTime = Temporal.Duration.from({ hours: 40, minutes: 30 });
const timePerTask = totalWorkTime.divide(5);
console.log(timePerTask);
// Temporal.Duration { hours: 8, minutes: 1, ... } // 40.5 hours / 5 = 8.1 hours. The 0.1 hours (6 minutes) is truncated.
const projectDuration = Temporal.Duration.from({ days: 90 });
const phaseDuration = projectDuration.divide(3);
console.log(phaseDuration);
// Temporal.Duration { days: 30, ... }
// Dividing by a negative number
const longDuration = Temporal.Duration.from({ years: 2 }).divide(-4);
console.log(longDuration);
// Temporal.Duration { years: -0, ... } // -0.5 years results in 0 years due to truncation.
// For more precise calculations involving division and fractional parts, consider using methods that operate on Temporal.Instant or Temporal.PlainDateTime.
7. Округлення тривалостей (round())
Метод round() є ключовим для нормалізації тривалостей, особливо при роботі з різними одиницями або коли потрібно виразити тривалість у певній одиниці з певною точністю. Він приймає одиницю та режим округлення як аргументи.
Поширені режими округлення включають:
Temporal.RoundingMode.trunc: Відкидає дробову частину (округлення до нуля).Temporal.RoundingMode.floor: Округлює до меншого.Temporal.RoundingMode.ceil: Округлює до більшого.Temporal.RoundingMode.halfExpand: Округлює до додатньої нескінченності, з округленням половинних значень від нуля.
const impreciseDuration = Temporal.Duration.from({
hours: 2,
minutes: 35,
seconds: 45
});
// Round to the nearest minute, using halfExpand (standard rounding)
const roundedToMinute = impreciseDuration.round('minute', Temporal.RoundingMode.halfExpand);
console.log(roundedToMinute);
// Temporal.Duration { hours: 2, minutes: 36, ... } // 35 minutes and 45 seconds rounds up to 36 minutes
// Truncate to the nearest hour
const truncatedToHour = impreciseDuration.round('hour', Temporal.RoundingMode.trunc);
console.log(truncatedToHour);
// Temporal.Duration { hours: 2, ... } // Discards the minutes and seconds.
// Round up to the nearest hour
const ceiledToHour = impreciseDuration.round('hour', Temporal.RoundingMode.ceil);
console.log(ceiledToHour);
// Temporal.Duration { hours: 3, ... } // Since there are minutes and seconds, it rounds up.
// Rounding to a smaller unit (e.g., to seconds) can reveal more precision
const preciseRounding = impreciseDuration.round('second', Temporal.RoundingMode.halfExpand);
console.log(preciseRounding);
// Temporal.Duration { hours: 2, minutes: 35, seconds: 45, ... }
8. Порівняння тривалостей (compare())
Статичний метод Temporal.Duration.compare() використовується для порівняння двох об'єктів Duration. Він повертає:
1якщо перша тривалість більша за другу.-1якщо перша тривалість менша за другу.0якщо тривалості рівні.
Порівняння виконується шляхом перетворення обох тривалостей до спільної найменшої одиниці (наносекунд) і подальшого порівняння їхніх числових значень. Це забезпечує точне порівняння незалежно від одиниць, використаних в оригінальних об'єктах тривалості.
const durationX = Temporal.Duration.from({ days: 1, hours: 12 }); // 1.5 days
const durationY = Temporal.Duration.from({ hours: 36 }); // 1.5 days
const durationZ = Temporal.Duration.from({ days: 2 }); // 2 days
console.log(Temporal.Duration.compare(durationX, durationY)); // 0 (equal)
console.log(Temporal.Duration.compare(durationX, durationZ)); // -1 (durationX is less than durationZ)
console.log(Temporal.Duration.compare(durationZ, durationY)); // 1 (durationZ is greater than durationY)
// Comparison with negative durations
const negDuration1 = Temporal.Duration.from({ hours: -5 });
const negDuration2 = Temporal.Duration.from({ hours: -10 });
console.log(Temporal.Duration.compare(negDuration1, negDuration2)); // 1 (e.g., -5 is greater than -10)
Робота з тривалостями та датами/часом
Хоча Temporal.Duration представляє кількість часу, його справжня корисність часто реалізується при поєднанні з конкретними точками в часі або об'єктами дати/часу, такими як Temporal.PlainDate, Temporal.PlainDateTime, Temporal.ZonedDateTime та Temporal.Instant. Арифметичні операції над цими об'єктами неявно використовуватимуть обчислення тривалості.
Додавання/віднімання тривалостей до/від дат/часу
Методи, такі як add() та subtract() на об'єктах дати/часу, приймають Duration як аргумент. Саме тут Temporal обробляє складнощі календарної арифметики (наприклад, високосні роки, місяці з різною кількістю днів).
// Example using Temporal.PlainDate (requires polyfill or native support)
// Assuming you have a Temporal polyfill or native support in your environment.
// Let's imagine today is July 15, 2024
const today = Temporal.PlainDate.from({ year: 2024, month: 7, day: 15 });
const durationToAdd = Temporal.Duration.from({ years: 1, months: 3, days: 15 });
const futureDate = today.add(durationToAdd);
console.log(futureDate);
// Temporal.PlainDate { year: 2025, month: 11, day: 1 }
// Global example: Calculating a future date considering different month lengths
const londonDate = Temporal.PlainDate.from({ year: 2024, month: 1, day: 31 }); // January 31st
const durationForNextMonth = Temporal.Duration.from({ months: 1 });
const nextMonthDate = londonDate.add(durationForNextMonth);
console.log(nextMonthDate);
// Temporal.PlainDate { year: 2024, month: 2, day: 29 } // Correctly handles leap year and end of month.
const newYorkDate = Temporal.ZonedDateTime.from({
timeZone: 'America/New_York',
year: 2024,
month: 10,
day: 28,
hour: 10,
minute: 0,
second: 0
});
const travelDuration = Temporal.Duration.from({ hours: 8 }); // An 8-hour flight
// Note: When adding durations to ZonedDateTime, it's crucial to consider the time zone.
// The result will be in the same time zone unless specified otherwise.
const arrivalTimeNY = newYorkDate.add(travelDuration);
console.log(arrivalTimeNY);
// Temporal.ZonedDateTime { year: 2024, month: 10, day: 28, hour: 18, minute: 0, second: 0, ... }
// If you want to calculate arrival time in a DIFFERENT time zone, you'd typically:
// 1. Add duration to the departure ZonedDateTime.
// 2. Convert the resulting ZonedDateTime to the destination time zone.
const tokyoTimeZone = 'Asia/Tokyo';
const arrivalTimeTokyo = arrivalTimeNY.withTimeZone(tokyoTimeZone);
console.log(arrivalTimeTokyo);
// Temporal.ZonedDateTime { year: 2024, month: 10, day: 29, hour: 7, minute: 0, second: 0, ... } (Note the date and time change due to time zone)
Обчислення тривалості між датами/часом
Методи until() та since() на об'єктах дати/часу повертають Temporal.Duration. Таким чином вимірюється час, що минув між двома точками.
const startDate = Temporal.PlainDate.from({ year: 2023, month: 1, day: 1 });
const endDate = Temporal.PlainDate.from({ year: 2024, month: 3, day: 15 });
const elapsedDuration = startDate.until(endDate);
console.log(elapsedDuration);
// Temporal.Duration { years: 1, months: 2, days: 14, ... }
// Global example: Calculating contract length difference
const contractStart = Temporal.ZonedDateTime.from({
timeZone: 'UTC',
year: 2022,
month: 5,
day: 10,
hour: 9,
minute: 0
});
const contractEnd = Temporal.ZonedDateTime.from({
timeZone: 'UTC',
year: 2025,
month: 8,
day: 20,
hour: 17,
minute: 30
});
const contractLength = contractStart.until(contractEnd);
console.log(contractLength);
// Temporal.Duration { years: 3, months: 3, days: 10, hours: 8, minutes: 30, ... }
// When using until/since with ZonedDateTime, the result can be complex due to time zones and DST.
// Temporal handles this by giving you a duration that may not 'round trip' perfectly if you just add it back without considering the time zone.
Найкращі практики та глобальні аспекти
При роботі з Temporal Durations, особливо в глобальному контексті, пам'ятайте про наступні моменти:
-
Незмінність є ключовою: Завжди розглядайте об'єкти
Durationяк незмінні. Будь-яка операція повертає новий об'єкт, запобігаючи непередбачуваним побічним ефектам. -
Розумійте різницю між агрегацією одиниць і календарною арифметикою: Арифметика з
Durationсама по собі виконує просту агрегацію одиниць. Коли ви поєднуєтеDurationз об'єктом дати/часу, методи Temporal (наприклад,add()наPlainDate) виконують календарно-залежну арифметику, яка є більш складною та враховує різну довжину місяців, високосні роки тощо. -
Часові пояси мають величезне значення: Для будь-якого застосунку, що працює з користувачами або подіями в різних регіонах, використання
Temporal.ZonedDateTimeє вкрай важливим. Сам об'єктDurationне залежить від часового поясу, але його застосування зZonedDateTimeвимагає обережного поводження для правильного представлення минулого часу в різних поясах. - ISO 8601 — ваш друг: Використовуйте рядки ISO 8601 для тривалостей, коли це можливо. Вони стандартизовані, однозначні та легко парсяться і генеруються, що робить їх ідеальними для обміну даними між системами та для міжнародної узгодженості.
-
Вибирайте відповідне округлення: Метод
round()є потужним, але вимагає розуміння ваших потреб у округленні. Для фінансових розрахунків можуть застосовуватися специфічні правила округлення. Для загального відображення часу зазвичай підходитьhalfExpand. - Враховуйте досвід користувача: При відображенні тривалостей користувачам, подумайте про локалізацію виводу. Хоча Temporal надає необроблену тривалість, представлення 'P1Y2M' як '1 рік і 2 місяці' або навіть '14 місяців' може бути більш зручним для користувача залежно від контексту та локалі.
- Дотримуйтесь стандарту: Temporal API розроблено, щоб стати стандартом. У міру того, як він набуває ширшого поширення та підтримки браузерами, опора на нього спростить ваш код і зробить його більш підтримуваним та перспективним.
Висновок
Temporal API в JavaScript з його об'єктом Duration є значним кроком уперед в обробці часових обчислень. Надаючи надійну, незмінну та математично обґрунтовану основу для арифметики тривалостей, він дозволяє розробникам створювати більш надійні та точні застосунки. Незалежно від того, чи ви керуєте міжнародними проєктами, розробляєте глобальні інструменти планування, чи просто потребуєте точних розрахунків часових інтервалів, опанування арифметики з Temporal Duration стане безцінною навичкою для будь-якого сучасного JavaScript-розробника.
Оскільки світ стає все більш взаємопов'язаним, здатність точно та інтуїтивно керувати часовими інтервалами в різних регіонах і контекстах — це вже не розкіш, а необхідність. Об'єкт Temporal.Duration є вашим ключем до розкриття цієї можливості, прокладаючи шлях до більш досконалих та глобально орієнтованих застосунків.